package sketch.shape.geom.prim;

import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;

import sketch.shape.Shape;
import sketch.shape.ShapeOption;
import sketch.shape.ShapePredicate;
import sketch.shape.geom.Rectangle;

public class Line extends Shape {

	private Line2D.Double line;

	public Line() {
		this(new Point(), new Point());
	}

	public Line(Point sp, Point ep) {
		line = new Line2D.Double(sp.toPoint2D(), ep.toPoint2D());
	}

	public Line(double x1, double y1, double x2, double y2) {
		line = new Line2D.Double(x1, y1, x2, y2);
	}

	public Line(Point sp, gVector dir) {
		this(sp, sp.add(dir));
	}

	public boolean contains(double arg0, double arg1, double arg2, double arg3) {
		return line.contains(arg0, arg1, arg2, arg3);
	}

	public boolean contains(double arg0, double arg1) {
		return line.contains(arg0, arg1);
	}

	public boolean contains(Point arg0) {
		return line.contains(arg0.toPoint2D());
	}

	public boolean contains(Rectangle arg0) {
		return line.contains(arg0.toRectangle2D());
	}

	public Rectangle getBounds() {
		return new Rectangle(line.getBounds());
	}

	public Rectangle2D getBounds2D() {
		return line.getBounds2D();
	}

	public Point getP1() {
		return new Point(line.getP1());
	}

	public Point getP2() {
		return new Point(line.getP2());
	}

	public PathIterator getPathIterator(AffineTransform arg0, double arg1) {
		return line.getPathIterator(arg0, arg1);
	}

	public PathIterator getPathIterator(AffineTransform arg0) {
		return line.getPathIterator(arg0);
	}

	public double getX1() {
		return line.getX1();
	}

	public double getX2() {
		return line.getX2();
	}

	public double getY1() {
		return line.getY1();
	}

	public double getY2() {
		return line.getY2();
	}

	public double getDx() {
		return Math.abs(getX1() - getX2());
	}

	public double getDy() {
		return Math.abs(getY1() - getY2());
	}

	public boolean intersects(double arg0, double arg1, double arg2, double arg3) {
		return line.intersects(arg0, arg1, arg2, arg3);
	}

	public boolean intersects(Rectangle arg0) {
		return line.intersects(arg0.toRectangle2D());
	}

	public boolean intersectsLine(double arg0, double arg1, double arg2, double arg3) {
		return line.intersectsLine(arg0, arg1, arg2, arg3);
	}

	public boolean intersectsLine(Line arg0) {
		return line.intersectsLine(arg0.toLine2D());
	}

	public double ptLineDist(double arg0, double arg1) {
		return line.ptLineDist(arg0, arg1);
	}

	public double ptLineDist(Point arg0) {
		return line.ptLineDist(arg0.toPoint2D());
	}

	public double ptLineDistSq(double arg0, double arg1) {
		return line.ptLineDistSq(arg0, arg1);
	}

	public double ptLineDistSq(Point arg0) {
		return line.ptLineDistSq(arg0.toPoint2D());
	}

	public double ptSegDist(double arg0, double arg1) {
		return line.ptSegDist(arg0, arg1);
	}

	public double ptSegDist(Point arg0) {
		return line.ptSegDist(arg0.toPoint2D());
	}

	public double ptSegDistSq(double arg0, double arg1) {
		return line.ptSegDistSq(arg0, arg1);
	}

	public double ptSegDistSq(Point arg0) {
		return line.ptSegDistSq(arg0.toPoint2D());
	}

	public int relativeCCW(double arg0, double arg1) {
		return line.relativeCCW(arg0, arg1);
	}

	public int relativeCCW(Point arg0) {
		return line.relativeCCW(arg0.toPoint2D());
	}

	public void setLine(double arg0, double arg1, double arg2, double arg3) {
		line.setLine(arg0, arg1, arg2, arg3);
	}

	public void setLine(Line arg0) {
		line.setLine(arg0.toLine2D());
	}

	public void setLine(Point arg0, Point arg1) {
		line.setLine(arg0.toPoint2D(), arg1.toPoint2D());
	}

	public Line2D toLine2D() {
		return new Line2D.Double(getX1(), getY1(), getX2(), getY2());
	}

	public void drawComponent(Graphics2D g2) {
		super.drawComponent(g2);
		g2.draw(toLine2D());
	}

	public gVector dir() {
		Point vec = getP2().subtract(getP1());
		return new gVector(vec.getX(), vec.getY());
	}

	public void setP1(Point p) {
		setLine(p, getP2());
	}

	public void setP2(Point p) {
		setLine(getP1(), p);
	}

	public void extendP1(double scale) {
		gVector vec = getP1().subtract(getP2()).dir();
		setLine(getP1().add(vec, scale), getP2());
	}

	public void extendP2(double scale) {
		gVector vec = getP2().subtract(getP1()).dir();
		setLine(getP1(), getP2().add(vec, scale));
	}

	public double length() {
		return getP1().distance(getP2());
	}

	public Line clone() {
		return new Line(getP1().clone(), getP2().clone());
	}

	public void switchPoints() {
		Point p1 = getP2();
		Point p2 = getP1();
		setLine(p1, p2);
	}

	public String toString() {
		return getP1() + "-" + getP2();
	}

	public java.awt.Shape getBound() {
		return getBound(ShapeOption.selectionGap);
	}

	public java.awt.Shape getBound(double GAP) {
		gVector dir = dir().unitized();
		double sqrt2 = Math.sqrt(2 * GAP * GAP);
		Point p[] = new Point[] {
			getP1().add(dir.rotate(0.75 * Math.PI).multiply(sqrt2)),
			getP1().add(dir.rotate(-0.75 * Math.PI).multiply(sqrt2)),
			getP2().add(dir.rotate(-0.25 * Math.PI).multiply(sqrt2)),
			getP2().add(dir.rotate(0.25 * Math.PI).multiply(sqrt2))
		};

		int x[] = new int[p.length], y[] = new int[p.length];
		for (int i = 0; i < p.length; i++) {
			x[i] = p[i].intX();
			y[i] = p[i].intY();
		}
		return new Polygon(x, y, p.length);
	}

//	public boolean contains(Line line) {
//		return ShapePredicate.contains(this, line);
//	}

	public Line reverse() {
		return new Line(getP2(), getP1());
	}

	public Line getLineStartWith(Point p) {
		if (ShapePredicate.nearPointPoint(p, getP1()))
			return clone();
		if (ShapePredicate.nearPointPoint(p, getP2()))
			return reverse();
		return new Line(p, new Point());
	}

	public Point getEndPointTo(Line line) {
		if (ShapePredicate.containsLinePoint(line, getP1()))
			return getP2();
		if (ShapePredicate.containsLinePoint(line, getP2()))
			return getP1();
		return new Point();
	}

	public Point getEndPointTo(Point p) {
		if (ShapePredicate.nearPointPoint(getP1(), p))
			return getP2();
		if (ShapePredicate.nearPointPoint(getP2(), p))
			return getP1();
		return new Point();
	}

	public Point getCommonPointTo(Point p) {
		if (ShapePredicate.nearPointPoint(getP1(), p))
			return getP1();
		if (ShapePredicate.nearPointPoint(getP2(), p))
			return getP2();
		return new Point();
	}

	public Point getCommonPointTo(Line line) {
		if (ShapePredicate.containsLinePoint(line, getP1()))
			return getP1();
		if (ShapePredicate.containsLinePoint(line, getP2()))
			return getP2();
		return new Point();
	}

	public String[][] getFieldValues() {
		return new String[][] {
				{ "P1", getP1().toString() },
				{ "P2", getP2().toString() }
		};
	}

	public double angleDWith(Line mate) {
		return dir().angleWith(mate.dir());
	}

	public double angleWith(Line mate) {
		double aD = angleDWith(mate);
		if (aD > Math.PI)
			aD -= Math.PI;
		return aD;
	}

	public Line getLineStartFrom(Point p) {
		double x1 = p.distance(getP1());
		double x2 = p.distance(getP2());
		if (x1 > x2) {
			return new Line(getP2(), getP1());
		} else {
			return new Line(getP1(), getP2());
		}
	}

	public Point linearInterpolate(double weightForP1) {
		double weightForP2 = 1.0 - weightForP1;
		return new Point(getX1() * weightForP2 + getX2() * weightForP1,
				getY1() * weightForP2 + getY2() * weightForP1);
	}

	public Point getCenter() {
		return linearInterpolate(0.5);
	}

	public Point intersection(Line mate) {
		if (!intersectsLine(mate))
			return new Point();

		double x1 = getX1(), y1 = getY1();
		double x2 = getX2(), y2 = getY2();
		double x3 = mate.getX1(), y3 = mate.getY1();
		double x4 = mate.getX2(), y4 = mate.getY2();

		double div = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

		double u1 = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
		u1 = u1 / div;

		double x = x1 + u1 * (x2 - x1);
		double y = y1 + u1 * (y2 - y1);

		return new Point(x, y);
	}
}
